@@ -99,6 +99,11 @@ urlpatterns += [  | 
            ||
| 99 | 99 | 
                url(r'^wx/notify_url$', pay_views.wx_notify_url_api, name='wx_notify_url_api'), # 支付异步通知回调地址  | 
            
| 100 | 100 | 
                ]  | 
            
| 101 | 101 | 
                 | 
            
| 102 | 
                +# 提现相关  | 
            |
| 103 | 
                +urlpatterns += [  | 
            |
| 104 | 
                + url(r'^wx/balance_withdraw$', pay_views.wx_balance_withdraw_api, name='wx_balance_withdraw_api'), # 余额提现: 企业付款/现金红包  | 
            |
| 105 | 
                +]  | 
            |
| 106 | 
                +  | 
            |
| 102 | 107 | 
                # 分享相关  | 
            
| 103 | 108 | 
                urlpatterns += [  | 
            
| 104 | 109 | 
                url(r'^wx/jsapi_signature$', wechat_views.wx_jsapi_signature_api, name='wx_jsapi_signature_api'), # jsapi_signature  | 
            
                @@ -261,6 +261,11 @@ WECHAT = {
               | 
            ||
| 261 | 261 | 
                'appsecret': '',  | 
            
| 262 | 262 | 
                'mchID': '',  | 
            
| 263 | 263 | 
                'apiKey': '',  | 
            
| 264 | 
                + 'mch_cert': '',  | 
            |
| 265 | 
                + 'mch_key': '',  | 
            |
| 266 | 
                +        'redpacket': {
               | 
            |
| 267 | 
                +  | 
            |
| 268 | 
                + }  | 
            |
| 264 | 269 | 
                },  | 
            
| 265 | 270 | 
                }  | 
            
| 266 | 271 | 
                 | 
            
                @@ -13,7 +13,7 @@ from account.models import LensmanIncomeExpensesInfo, LensmanInfo, UserIncomeExp  | 
            ||
| 13 | 13 | 
                from group.models import GroupPhotoInfo, GroupPhotoOrderInfo  | 
            
| 14 | 14 | 
                from pay.models import OrderInfo  | 
            
| 15 | 15 | 
                from photo.models import PhotosInfo  | 
            
| 16 | 
                -from utils.error.errno_utils import GroupPhotoStatusCode, OrderStatusCode  | 
            |
| 16 | 
                +from utils.error.errno_utils import GroupPhotoStatusCode, OrderStatusCode, UserStatusCode, WithdrawStatusCode  | 
            |
| 17 | 17 | 
                from utils.error.response_utils import response  | 
            
| 18 | 18 | 
                from utils.page_utils import pagination  | 
            
| 19 | 19 | 
                from utils.redis.rkeys import LENSMAN_PHOTO_PRICE  | 
            
                @@ -329,3 +329,42 @@ def wx_notify_url_api(request):  | 
            ||
| 329 | 329 | 
                order_paid_success(order)  | 
            
| 330 | 330 | 
                 | 
            
| 331 | 331 | 
                return HttpResponse(settings.WXPAY_NOTIFY_SUCCESS)  | 
            
| 332 | 
                +  | 
            |
| 333 | 
                +  | 
            |
| 334 | 
                +def wx_balance_withdraw_api(request):  | 
            |
| 335 | 
                +    user_id = request.POST.get('user_id', '')
               | 
            |
| 336 | 
                +  | 
            |
| 337 | 
                + # 用户校验  | 
            |
| 338 | 
                + try:  | 
            |
| 339 | 
                + user = UserInfo.objects.get(user_id=user_id)  | 
            |
| 340 | 
                + except UserInfo.DoesNotExist:  | 
            |
| 341 | 
                + return response(UserStatusCode.USER_NOT_FOUND)  | 
            |
| 342 | 
                +  | 
            |
| 343 | 
                + # JSAPI--公众号支付、NATIVE--原生扫码支付、APP--app支付,统一下单接口trade_type的传参可参考这里  | 
            |
| 344 | 
                +    trade_type = request.POST.get('trade_type', '')
               | 
            |
| 345 | 
                + # TRANSFER--企业付款、PACKET--现金红包, 余额提现接口withdraw_type的传参可参考这里  | 
            |
| 346 | 
                +    withdraw_type = request.POST.get('withdraw_type', 'TRANSFER')
               | 
            |
| 347 | 
                +    amount = int(request.POST.get('amount', 0))
               | 
            |
| 348 | 
                +  | 
            |
| 349 | 
                + if user.balance < amount:  | 
            |
| 350 | 
                + return response(WithdrawStatusCode.BALANCE_NOT_ENOUGH)  | 
            |
| 351 | 
                +  | 
            |
| 352 | 
                + # 根据 trade_type 获取 wechat 配置  | 
            |
| 353 | 
                +    wechat = WECHAT.get(trade_type, {})
               | 
            |
| 354 | 
                + # WeChatPay 初始化  | 
            |
| 355 | 
                +    wxpay = WeChatPay(wechat.get('appID'), wechat.get('apiKey'), wechat.get('mchID'), mch_cert=wechat.get('mch_cert'), mch_key=wechat.get('mch_key'))
               | 
            |
| 356 | 
                +  | 
            |
| 357 | 
                + if withdraw_type == 'TRANSFER':  | 
            |
| 358 | 
                + wxpay.transfer.transfer(user.wx_uid, amount, u'摄影师余额提现,企业付款', check_name='NO_CHECK')  | 
            |
| 359 | 
                + elif withdraw_type == 'PACKET':  | 
            |
| 360 | 
                + wxpay.redpack.send(  | 
            |
| 361 | 
                + user.wx_uid,  | 
            |
| 362 | 
                + amount,  | 
            |
| 363 | 
                +            send_name=wechat.get('redpacket', {}).get('SEND_NAME'),
               | 
            |
| 364 | 
                +            nick_name=wechat.get('redpacket', {}).get('NICK_NAME'),
               | 
            |
| 365 | 
                +            act_name=wechat.get('redpacket', {}).get('ACT_NAME'),
               | 
            |
| 366 | 
                +            wishing=wechat.get('redpacket', {}).get('WISHING'),
               | 
            |
| 367 | 
                +            remark=wechat.get('redpacket', {}).get('REMARK'),
               | 
            |
| 368 | 
                + )  | 
            |
| 369 | 
                +  | 
            |
| 370 | 
                +    return response(200, 'Withdraw Success', u'提现成功', {})
               | 
            
                @@ -75,6 +75,11 @@ class OrderStatusCode(BaseStatusCode):  | 
            ||
| 75 | 75 | 
                NO_DETAIL_PERMISSION = StatusCodeField(404015, u'No Detail Permission', description=u'没有详情权限')  | 
            
| 76 | 76 | 
                 | 
            
| 77 | 77 | 
                 | 
            
| 78 | 
                +class WithdrawStatusCode(BaseStatusCode):  | 
            |
| 79 | 
                + """ 提现相关错误码 4041xx """  | 
            |
| 80 | 
                + BALANCE_NOT_ENOUGH = StatusCodeField(404100, u'Balance Not Enough', description=u'提现金额不足')  | 
            |
| 81 | 
                +  | 
            |
| 82 | 
                +  | 
            |
| 78 | 83 | 
                class MessageStatusCode(BaseStatusCode):  | 
            
| 79 | 84 | 
                """ 消息相关错误码 4090xx """  | 
            
| 80 | 85 | 
                MESSAGE_NOT_FOUND = StatusCodeField(409001, u'Message Not Found', description=u'消息不存在')  | 
            
                @@ -2,11 +2,18 @@  | 
            ||
| 2 | 2 | 
                 | 
            
| 3 | 3 | 
                from django.http import JsonResponse  | 
            
| 4 | 4 | 
                 | 
            
| 5 | 
                +from utils.error.errno_utils import StatusCodeField  | 
            |
| 5 | 6 | 
                 | 
            
| 6 | 
                -def response(status_code, data={}):
               | 
            |
| 7 | 
                -    return JsonResponse({
               | 
            |
| 7 | 
                +  | 
            |
| 8 | 
                +def response_data(status_code, message=None, description=None, data={}):
               | 
            |
| 9 | 
                +    return {
               | 
            |
| 8 | 10 | 
                'status': status_code,  | 
            
| 9 | 
                - 'message': status_code.message,  | 
            |
| 10 | 
                - 'description': status_code.description,  | 
            |
| 11 | 
                + 'message': message,  | 
            |
| 12 | 
                + 'description': description,  | 
            |
| 11 | 13 | 
                'data': data,  | 
            
| 12 | 
                - })  | 
            |
| 14 | 
                + }  | 
            |
| 15 | 
                +  | 
            |
| 16 | 
                +  | 
            |
| 17 | 
                +def response(status_code, message=None, description=None, data={}):
               | 
            |
| 18 | 
                + message, description = (status_code.message, status_code.description) if isinstance(status_code, StatusCodeField) else (message, description)  | 
            |
| 19 | 
                + return JsonResponse(response_data(status_code, message, description, data))  |